﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Input;

namespace lumo.input
{
    /// <summary>
    /// A cursor object
    /// </summary>
    public class Cursor : IUpdatableOnce
    {
        /// <summary>
        /// The button enumeration.
        /// </summary>
        public enum Button
        {
            /// <summary>Left Cursor Button</summary>
            Left,

            /// <summary>Middle Cursor Button</summary>
            Middle,

            /// <summary>Right Cursor Button</summary>
            Right,

            /// <summary>X1 Button</summary>
            X1,

            /// <summary>X2 Button</summary>
            X2
        }

        /// <summary>
        /// CursorInputObject
        /// </summary>
        abstract class CursorInputObject : IInputObject
        {
            internal InputType InputType = InputType.None;
            internal long InputID = 0;
            internal Cursor CursorParent;
            private bool LastDown = false;
            private bool CurrentDown = false;
            private float RepeatCount = 0;
            private float _RepeatDelay = Config.DefaultRepeatDelay;
            private float _RepeatTime = Config.DefaultRepeatTime;

            // The propertie for frame ID
            private uint _FrameID = 0;
            /// <summary>Access for FrameID</summary>
            public uint FrameID { get { return _FrameID; } }

            /// <summary>
            /// Constructor for Input object.
            /// </summary>
            /// <param name="CursorParent">The parent Cursor</param>
            public CursorInputObject(Cursor CursorParent)
            {
                this.CursorParent = CursorParent;
                Repeated = false;
            }

            /// <summary>
            /// Initialize the input status
            /// </summary>
            internal void InitializeStatus()
            {
                CurrentDown = LastDown = IsDown();
            }

            /// <summary>
            /// Check if it is down.
            /// </summary>
            /// <returns>True if it is down</returns>
            internal abstract bool IsDown();

            /// <summary>
            /// Update the key.
            /// </summary>
            /// <param name="Game">The game object (Not Needed)</param>
            public void Update(LumoComponent Game)
            {
                CursorParent.Update(Game);
            }

            /// <summary>
            /// Internal update called on Cursor.Update
            /// </summary>
            internal virtual void InternalUpdate(LumoComponent Game)
            {
                // Only called once per frame.
                if (_FrameID == Game.FrameID)
                    return;
                _FrameID = Game.FrameID;

                LastDown = CurrentDown;
                CurrentDown = IsDown();
                RepeatUpdate(Game);
            }

            /// <summary>
            /// Internal Repeat Update
            /// </summary>
            private void RepeatUpdate(LumoComponent Game)
            {
                Repeated = false;
                if (Game == null)
                {
                    Repeated = Triggered;
                    return;
                }

                if (CurrentDown)
                {
                    if (!LastDown)
                    {
                        Repeated = true;
                        RepeatCount = _RepeatDelay;
                    }
                    else
                    {
                        RepeatCount -= (float)Game.GameTime.ElapsedGameTime.TotalMilliseconds;
                        if (RepeatCount <= 0)
                        {
                            Repeated = true;
                            RepeatCount = _RepeatTime;
                        }
                    }
                }
            }

            /// <summary>
            /// Type of object.
            /// </summary>
            /// <returns>The type of object.</returns>
            public InputType GetInputType() { return InputType; }

            /// <summary>
            /// Return the Button.
            /// </summary>
            /// <returns>The ID.</returns>
            public long GetInputID() { return InputID; }

            /// <summary>
            /// Property to check if key is pressed.
            /// </summary>
            public bool Pressed { get { return CurrentDown; } }

            /// <summary>
            /// Property to check if key is triggered.
            /// </summary>
            public bool Triggered { get { return (CurrentDown && !LastDown); } }

            /// <summary>
            /// Property to check if key is repeated.
            /// </summary>
            public bool Repeated { get; private set; }

            /// <summary>
            /// Property to check if key is released.
            /// </summary>
            public bool Released { get { return (LastDown && !CurrentDown); } }

            /// <summary>
            /// Implemented IInputObject Released.
            /// </summary>
            /// <returns>True if the key is released.</returns>
            public float RepeatDelay { get { return _RepeatDelay; } set { _RepeatDelay = value; } }

            /// <summary>
            /// Implemented IInputObject Released.
            /// </summary>
            /// <returns>True if the key is released.</returns>
            public float RepeatTime { get { return _RepeatTime; } set { _RepeatTime = value; } }
        }

        /// <summary>
        /// CursorButton object
        /// </summary>
        class CursorButton : CursorInputObject, IInputObject
        {
            private Button Button;
            private Func<MouseState, bool> Function;

            /// <summary>
            /// Constructor for key.
            /// </summary>
            /// <param name="CursorParent">The parent Cursor</param>
            /// <param name="Button">The Button</param>
            /// <param name="Function">The function.</param>
            public CursorButton(Cursor CursorParent, Button Button, Func<MouseState, bool> Function)
                : base(CursorParent)
            {
                this.InputType = InputType.CursorButton;
                this.InputID = (long)Button;
                this.Button = Button;
                this.Function = Function;
                InitializeStatus();
            }

            /// <summary>
            /// Check if the key is down.
            /// </summary>
            /// <returns>True if it is down</returns>
            internal override bool IsDown() { return Function(CursorParent.State); }
        }

        // Private variables
        private Dictionary<Button, CursorButton> CursorButtonDictionary;
        private MouseState State;
        private float      AccumDeltaWheel = 0;
        private int LastX = -1;
        private int LastY = -1;

        // The propertie for frame ID
        private uint _FrameID = 0;
        /// <summary>Access for FrameID</summary>
        public uint FrameID { get { return _FrameID; } }

        /// <summary>
        /// UPdate the state
        /// </summary>
        public Cursor()
        {
            // Add the buttons
            State = Mouse.GetState();
            CursorButtonDictionary = new Dictionary<Button, CursorButton>();
            CursorButtonDictionary.Add(Button.Left,   new CursorButton(this, Button.Left, state => (state.LeftButton == ButtonState.Pressed)));
            CursorButtonDictionary.Add(Button.Middle, new CursorButton(this, Button.Right, state => (state.MiddleButton == ButtonState.Pressed)));
            CursorButtonDictionary.Add(Button.Right,  new CursorButton(this, Button.Middle, state => (state.RightButton == ButtonState.Pressed)));
            CursorButtonDictionary.Add(Button.X1,     new CursorButton(this, Button.X1, state => (state.XButton1 == ButtonState.Pressed)));
            CursorButtonDictionary.Add(Button.X2,     new CursorButton(this, Button.X2, state => (state.XButton2 == ButtonState.Pressed)));

            // Update the state
            UpdateState();
            this.DeltaWheel = AccumDeltaWheel;
            this.AccumDeltaWheel = 0;

            // Check if cursor is idle.
            this.Moving = false;
        }

        /// <summary>
        /// UPdate the state
        /// </summary>
        private void UpdateState()
        {
            MouseState oldState = State;
            State = Mouse.GetState();
            this.AccumDeltaWheel += State.ScrollWheelValue - oldState.ScrollWheelValue;
            if (LastX != State.X || LastY != State.Y)
            {
                LastX = State.X;
                LastY = State.Y;
                this.Moving = true;
            }
            else
                this.Moving = false;
        }

        /// <summary>
        /// Get an specific button.
        /// </summary>
        public IInputObject GetButton(Button Button) { return CursorButtonDictionary[Button]; }

        /// <summary>
        /// Get all the buttons.
        /// </summary>
        public IEnumerable<IInputObject> GetButtons() { return CursorButtonDictionary.Values; }

        /// <summary>
        /// Get the delta wheel.
        /// </summary>
        public float DeltaWheel { get; private set; }

        /// <summary>
        /// Get the X cursor position.
        /// </summary>
        public int X { get { return State.X; } }

        /// <summary>
        /// Get the Y cursor position.
        /// </summary>
        public int Y { get { return State.Y; } }

        /// <summary>
        /// Check if moving.
        /// </summary>
        public bool Moving { get; private set; }

        /// <summary>
        /// Update the cursor.
        /// </summary>
        /// <param name="Game">The game object</param>
        public void Update(LumoComponent Game)
        {
            // Check for already updated object
            if (_FrameID == Game.FrameID)
                return;
            _FrameID = Game.FrameID;

            // Update the state.
            UpdateState();
            this.DeltaWheel = AccumDeltaWheel;
            AccumDeltaWheel = 0;

            //  Update every button
            foreach (CursorButton btn in CursorButtonDictionary.Values)
                btn.InternalUpdate(Game);
        }
    }
}
